Recall that code reuse comes in two flavors. You have just explored the classical “is-a” relationship. Before you examine the third pillar of OOP (polymorphism), let’s examine the “has-a” relationship (also known as the containment/delegation model or aggregation). Assume you have created a new class that models an employee benefits package:
// This new type will function as a contained class. class BenefitPackage { // Assume we have other members that represent // dental/health benefits, and so on. public double ComputePayDeduction() { return 125.0; } }
Obviously, it would be rather odd to establish an “is-a” relationship between the BenefitPackage class and the employee types. (Employee “is-a” BenefitPackage? I don’t think so.) However, it should be clear that some sort of relationship between the two could be established. In short, you would like to express the idea that each employee “has-a” BenefitPackage. To do so, you can update the Employee class definition as follows:
// Employees now have benefits. partial class Employee { // Contain a BenefitPackage object. protected BenefitPackage empBenefits = new BenefitPackage(); ... }
At this point, you have successfully contained another object. However, to expose the functionality of the contained object to the outside world requires delegation. Delegation is simply the act of adding public members to the containing class that make use of the contained object’s functionality.
For example, you could update the Employee class to expose the contained empBenefits object using a custom property as well as make use of its functionality internally using a new method named GetBenefitCost():
public partial class Employee { // Contain a BenefitPackage object. protected BenefitPackage empBenefits = new BenefitPackage(); // Expose certain benefit behaviors of object. public double GetBenefitCost() { return empBenefits.ComputePayDeduction(); } // Expose object through a custom property. public BenefitPackage Benefits { get { return empBenefits; } set { empBenefits = value; } } ... }
In the following updated Main() method, notice how you can interact with the internal BenefitsPackage type defined by the Employee type:
static void Main(string[] args) { Console.WriteLine("***** The Employee Class Hierarchy *****\n"); Manager chucky = new Manager("Chucky", 50, 92, 100000, "333-23-2322", 9000); double cost = chucky.GetBenefitCost(); Console.ReadLine(); }
The previous chapter briefly mentioned the concept of nested types, which is a spin on the “has-a” relationship you have just examined. In C# (as well as other .NET languages), it is possible to define a type (enum, class, interface, struct, or delegate) directly within the scope of a class or structure. When you have done so, the nested (or “inner”) type is considered a member of the nesting (or “outer”) class, and in the eyes of the runtime can be manipulated like any other member (fields, properties, methods, and events). The syntax used to nest a type is quite straightforward:
public class OuterClass { // A public nested type can be used by anybody. public class PublicInnerClass {} // A private nested type can only be used by members // of the containing class. private class PrivateInnerClass {} }
Although the syntax is clean, understanding why you might want to do this may not readily apparent. To understand this technique, ponder the following traits of nesting a type:
Nested types allow you to gain complete control over the access level of the inner type, as they may be declared privately (recall that non-nested classes cannot be declared using the private keyword).
Because a nested type is a member of the containing class, it can access private members of the containing class.
Oftentimes, a nested type is only useful as a helper for the outer class, and is not intended for use by the outside world.
When a type nests another class type, it can create member variables of the type, just as it would for any point of data. However, if you wish to make use of a nested type from outside of the containing type, you must qualify it by the scope of the nesting type. Consider the following code:
static void Main(string[] args) { // Create and use the public inner class. OK! OuterClass.PublicInnerClass inner; inner = new OuterClass.PublicInnerClass(); // Compiler Error! Cannot access the private class. OuterClass.PrivateInnerClass inner2; inner2 = new OuterClass.PrivateInnerClass(); }
To make use of this concept within the employees example, assume you have now nested the BenefitPackage directly within the Employee class type:
partial class Employee { public class BenefitPackage { // Assume we have other members that represent // dental/health benefits, and so on. public double ComputePayDeduction() { return 125.0; } } ... }
The nesting process can be as “deep” as you require. For example, assume you wish to create an enumeration named BenefitPackageLevel, which documents the various benefit levels an employee may choose. To programmatically enforce the tight connection between Employee, BenefitPackage, and BenefitPackageLevel, you could nest the enumeration as follows:
// Employee nests BenefitPackage. public partial class Employee { // BenefitPackage nests BenefitPackageLevel. public class BenefitPackage { public enum BenefitPackageLevel { Standard, Gold, Platinum } public double ComputePayDeduction() { return 125.0; } } ... }
Because of the nesting relationships, note how you are required to make use of this enumeration:
static void Main(string[] args) { ... // Define my benefit level. Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel = Employee.BenefitPackage.BenefitPackageLevel.Platinum; Console.ReadLine(); }
Excellent! At this point you have been exposed to a number of keywords (and concepts) that allow you to build hierarchies of related types via classical inheritance, containment, and nested types. If the details aren’t crystal clear right now, don’t sweat it. You will be building a number of additional hierarchies over the remainder of this text. Next up, let’s examine the final pillar of OOP: polymorphism.